use core::f32;
use std::{
    collections::HashMap, ptr, sync::OnceLock,
};
use bittle::{Bits, BitsMut};
use crate::{
    engine, game::*, imports::js,
};

const UCT_TIMES_MIN:usize = 1024;
const UCT_TIMES_MAX:usize = 1048576;
const LEVEL_MIN:usize = 1;
const LEVEL_MAX:usize = 16;
const LEVEL_UCT_MAP:[usize;10] = [
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
];
const LEVEL_FUCK_MAP:[usize;10] = [
    5, 6, 7, 8, 9, 10, 11, 12, 13, 14
];

const UCB_C:f32 = 1.6487212707001281;                   // C = (sqrt e)
const UCB_C_END:f32 = 1.2;
const UCB_C_DW:f32 = UCB_C - UCB_C_END;

const UCT_SIMULATE_DRAW:f32 = 0.1;
const UCT_SIMULATE_HASH_SIZE:usize = 1048576;
const UCT_SA_1:[u8;BOARD_LEN] = [
    0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
    1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    1, 1, 0, 0, 0, 0, 0, 0, 1, 1,
    0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
];
const UCT_SA_2:[u8;BOARD_LEN] = [
    0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
    1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
    1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 1, 1, 0, 0, 1, 1, 0, 0,
];
const UCT_SA_3:[u8;BOARD_LEN] = [
    0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
    1, 0, 0, 0, 0, 0, 0, 0, 0, 1,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 1, 1, 0, 0, 0, 0,
];
const UCT_SA_4:[u8;BOARD_LEN] = [
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 0, 1, 0, 0, 0, 0, 1, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
const UCT_SA_5:[u8;BOARD_LEN] = [
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 1, 0, 0, 0, 0, 0, 0, 1, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
const UCT_LTS:[&[u8;BOARD_LEN];6] = [&LOC_IN_CORNER, &UCT_SA_1, &UCT_SA_2, &UCT_SA_3, &UCT_SA_4, &UCT_SA_5];
const VALUATION_WHOLE_LOCS:[[f32;BOARD_LEN];2] = [
    [
        0.33672479, 0.74537951, 0.88748819, 0.96280789, 1.00000000, 1.00000000, 0.96280789, 0.88748819, 0.74537951, 0.33672479,
        0.74537951, 0.13533436, 0.12213030, 0.15804711, 0.17307951, 0.17307951, 0.15804711, 0.12213030, 0.13533436, 0.74537951,
        0.88748819, 0.12213030, 0.00000000, 0.03530632, 0.08158891, 0.08158891, 0.03530632, 0.00000000, 0.12213030, 0.88748819,
        0.96280789, 0.15804711, 0.03530632, 0.02180618, 0.09294090, 0.09294090, 0.02180618, 0.03530632, 0.15804711, 0.96280789,
        1.00000000, 0.17307951, 0.08158891, 0.09294090, 0.06568705, 0.06568705, 0.09294090, 0.08158891, 0.17307951, 1.00000000,
        1.00000000, 0.17307951, 0.08158891, 0.09294090, 0.06568705, 0.06568705, 0.09294090, 0.08158891, 0.17307951, 1.00000000,
        0.96280789, 0.15804711, 0.03530632, 0.02180618, 0.09294090, 0.09294090, 0.02180618, 0.03530632, 0.15804711, 0.96280789,
        0.88748819, 0.12213030, 0.00000000, 0.03530632, 0.08158891, 0.08158891, 0.03530632, 0.00000000, 0.12213030, 0.88748819,
        0.74537951, 0.13533436, 0.12213030, 0.15804711, 0.17307951, 0.17307951, 0.15804711, 0.12213030, 0.13533436, 0.74537951,
        0.33672479, 0.74537951, 0.88748819, 0.96280789, 1.00000000, 1.00000000, 0.96280789, 0.88748819, 0.74537951, 0.33672479,
    ],
    [
        0.26000094, 0.78594041, 0.91114247, 0.97283864, 1.00000000, 1.00000000, 0.97283864, 0.91114247, 0.78594041, 0.26000094,
        0.78594041, 0.39330286, 0.27090225, 0.23396978, 0.22740638, 0.22740638, 0.23396978, 0.27090225, 0.39330286, 0.78594041,
        0.91114247, 0.27090225, 0.07444751, 0.06831530, 0.07040359, 0.07040359, 0.06831530, 0.07444751, 0.27090225, 0.91114247,
        0.97283864, 0.23396978, 0.06831530, 0.00000000, 0.06163505, 0.06163505, 0.00000000, 0.06831530, 0.23396978, 0.97283864,
        1.00000000, 0.22740638, 0.07040359, 0.06163505, 0.01605090, 0.01605090, 0.06163505, 0.07040359, 0.22740638, 1.00000000,
        1.00000000, 0.22740638, 0.07040359, 0.06163505, 0.01605090, 0.01605090, 0.06163505, 0.07040359, 0.22740638, 1.00000000,
        0.97283864, 0.23396978, 0.06831530, 0.00000000, 0.06163505, 0.06163505, 0.00000000, 0.06831530, 0.23396978, 0.97283864,
        0.91114247, 0.27090225, 0.07444751, 0.06831530, 0.07040359, 0.07040359, 0.06831530, 0.07444751, 0.27090225, 0.91114247,
        0.78594041, 0.39330286, 0.27090225, 0.23396978, 0.22740638, 0.22740638, 0.23396978, 0.27090225, 0.39330286, 0.78594041,
        0.26000094, 0.78594041, 0.91114247, 0.97283864, 1.00000000, 1.00000000, 0.97283864, 0.91114247, 0.78594041, 0.26000094,
    ],
];
const VALUATION_WHOLE_LOC_MAXS:[f32;2] = [36.30836105, 39.79566193];
const N_PIECES_IN_MID:usize = 13;
const N_PIECES_IN_FUCK:usize = 65;
const N_PIECES_IN_ENDING:usize = 85;
const LOCS_BEGIN_RANGOM:[u8;BOARD_LEN] = [
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
    0, 0, 1, 1, 1, 1, 1, 1, 0, 0,
    0, 0, 1, 1, 1, 1, 1, 1, 0, 0,
    0, 0, 1, 1, 1, 1, 1, 1, 0, 0,
    0, 0, 1, 1, 1, 1, 1, 1, 0, 0,
    0, 0, 0, 1, 1, 1, 1, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];

fn valuation_n_pieces(n_black:usize, n_white:usize) -> f32 {
    if n_black + n_white >= BOARD_LEN {
        if n_black == n_white {
            0.0
        } else if n_black < n_white {
            1.0
        } else {
            -1.0
        }
    } else {
        let n_diff = (n_white as f32) - (n_black as f32);
        let diff_way = (2.0 * (n_diff >= 0.0) as i32 as f32) - 1.0;
        (1.0 - (1.0 / (1.0 + n_diff.abs()))) * diff_way
    }
}
fn valuation_whole(board:&Board, pcur:Piece) -> f32 {
    let pi = pcur as usize - 1;
    let locs = &VALUATION_WHOLE_LOCS[pi];
    let mut score = 0f32;
    for i in 0..BOARD_LEN {
        let p = board.get(i as Loc);
        let hit = (p == pcur) as i32 as f32;
        score += hit * locs[i];
    }
    score / VALUATION_WHOLE_LOC_MAXS[pi]
}
fn valuation_being(board:&Board) -> f32 {
    let n_black = board.n_black() as f32;
    let n_black_actives = board.moves_nums(Piece::Black) as f32;
    let score_black = (1.0 - (n_black / (BOARD_LEN as f32))) * n_black_actives;
    let n_white = board.n_white() as f32;
    let n_white_actives = board.moves_nums(Piece::White) as f32;
    let score_white = (1.0 - (n_white / (BOARD_LEN as f32))) * n_white_actives;
    score_white - score_black
}

pub struct Params {
    pub uct_times: usize,
    pub level: usize,
}
impl Default for Params {
    fn default() -> Self {
        Self {
            uct_times: UCT_TIMES_MIN,
            level: LEVEL_MIN,
        }
    }
}

#[allow(static_mut_refs)]
static mut PARAMS:OnceLock<Params> = OnceLock::new();

struct Node {
    parent: *mut Node,
    pcur: Piece,
    loc: Loc,
    n_select: f32,
    n_avg_wins: f32,
    n_avg_whole: f32,
    puct_p: f32,
    nexts: Vec<Node>,
    next_moves: Vec<Loc>,
    next_puct_ps: Vec<f32>,
    has_over: bool,
}

impl Node {
    fn new(parent:*mut Node, pcur:Piece, board:&Board, loc:Loc, puct_p:f32) -> Self {
        let n_empty = BOARD_LEN - board.n_black() - board.n_white();
        let next_moves:Vec<Loc> = if n_empty > 0 {
            board.moves(pcur.side())
        } else {
            Vec::new()
        };
        let mut whole_sum = 0.0;
        let next_wholes:Vec<f32> = next_moves.iter().map(|loc|{
            let mut board = board.clone();
            board.down(*loc, pcur.side());
            let whole = valuation_whole(&board, pcur.side());
            whole_sum += whole;
            whole
        }).collect();
        let next_puct_ps = next_wholes.iter().map(|a|{
            *a / whole_sum
        }).collect();
        Self {
            parent, pcur, loc,
            n_select: 0.0, n_avg_wins: 0.0, n_avg_whole: 0.0, puct_p,
            nexts: Vec::new(), next_moves, next_puct_ps, has_over: (n_empty < 1),
        }
    }
    fn expend(&mut self, board:&mut Board) -> &mut Node {
        let side = self.pcur.side();
        let mut loc = engine::loc_invalid();
        let mut puct_p = 0.0;;
        if self.next_moves.len() > 0 {
            let hit_i = js::random_range_u(0, self.next_moves.len());
            loc = self.next_moves[hit_i];
            puct_p = self.next_puct_ps[hit_i];
            self.next_puct_ps.swap_remove(hit_i);
            self.next_moves.swap_remove(hit_i);
            board.down(loc, side);
        }
        let next = Node::new(self, side, board, loc, puct_p);
        self.nexts.push(next);
        self.nexts.last_mut().unwrap()
    }
    #[inline] fn has_over(&self) -> bool {self.has_over}
    #[inline] fn has_fully(&self) -> bool {self.next_moves.len() < 1 && self.nexts.len() > 0}
    #[inline] fn avg_wins(&self) -> f32 {self.n_avg_wins / self.n_select}
    #[inline] fn avg_whole(&self) -> f32 {self.n_avg_whole / self.n_select}
    #[inline] fn puct_p(&self) -> f32 {self.puct_p}
}

// UCB-Improved
fn puct<'a>(node:&'a mut Node, ucb_c:f32) -> &'a mut Node {
    let mut max_bonus = f32::MIN;
    let mut max_node_i = 0usize;
    let node_sel:f32 = node.n_select.sqrt();
    for i in 0..node.nexts.len() {
        let next = &node.nexts[i];
        if next.n_select < 1.0 {
            return &mut node.nexts[i]
        }
        let ucb_c = if next.avg_wins() < UCT_SIMULATE_DRAW {UCB_C} else {ucb_c};
        let ucb_explore = ucb_c * next.puct_p() * (node_sel / next.n_select);
        let ucb_rewind = next.avg_wins();
        let bonus = ucb_explore + ucb_rewind;
        if max_bonus < bonus {
            max_bonus = bonus;
            max_node_i = i;
        }
    }
    &mut node.nexts[max_node_i]
}

fn uct_select<'a>(node:&'a mut Node, board:&mut Board, ucb_c:f32) -> &'a mut Node {
    if node.has_over() {
        node
    } else if node.has_fully() {
        let next = puct(node, ucb_c);
        if engine::loc_in_board(next.loc) {
            board.down(next.loc, next.pcur);
        }
        uct_select(next, board, ucb_c)
    } else {
        node.expend(board)
    }
}

fn uct_value_unfill(board:&Board) -> f32 {
    let n_black = board.n_black();
    let n_white = board.n_white();
    if n_black > n_white {
        -1.0
    } else if n_white > n_black {
        1.0
    } else {
        UCT_SIMULATE_DRAW
    }
}

fn uct_value(board:&Board) -> Option<f32> {
    let n_black = board.n_black();
    let n_white = board.n_white();
    if n_black + n_white < BOARD_LEN {
        None
    } else if n_black > n_white {
        Some(-1.0)
    } else if n_white > n_black {
        Some(1.0)
    } else {
        Some(UCT_SIMULATE_DRAW)
    }
}

#[derive(Clone)]
struct SimulateResult {
    end_way: f32,               // -1.0 lose < draw < 1.0 win
    whole: f32,
    board: Board,
}
impl Default for SimulateResult {
    fn default() -> Self {
        Self {end_way: 0.0, whole: 0.0, board: Default::default()}
    }
}
fn uct_simulate(time_board:&mut Board, node:&Node) -> SimulateResult {
    let mut sr = SimulateResult::default();
    let mut n_pass = 0;
    let mut side = node.pcur.side();
    let mut sim_board = time_board.clone();
    while n_pass < 1 {
        if let Some(value) = uct_value(&sim_board) {
            sr.end_way = value * node.pcur.way();
            break
        }
        let moves = sim_board.moves(side);
        if moves.len() > 0 {
            let next_loc = uct_rollout(&moves, side);
            sim_board.down(next_loc, side);
            n_pass = 0;
        } else {
            n_pass += 1;
            if n_pass > 1 {
                sr.end_way = uct_value_unfill(&sim_board) * node.pcur.way();
            }
        }
        side = side.side();
    }
    sr.whole = valuation_whole(&sim_board, node.pcur);
    sr.board = sim_board.clone();
    sr
}
fn uct_rollout(moves:&[Loc], pcur:Piece) -> Loc {
    for lt in UCT_LTS {
        let hit:Vec<Loc> = moves.iter().filter_map(|a|{if lt[*a as usize] > 0 {Some(*a)} else {None}}).collect();
        if (! hit.is_empty()) && js::random_range_u(0, 2) == 0 {
            return hit[js::random_range_u(0, hit.len())];
        }
    }
    let moves:Vec<Loc> = moves.into();
    let values = VALUATION_WHOLE_LOCS[pcur as usize - 1];
    let mut max = (0, values[moves[0] as usize]);
    for i in 1..moves.len() {
        let v = values[moves[i] as usize];
        if v > max.1 {
            max = (i, v);
        }
    }
    moves[max.0]
}

fn uct_backup(node:&mut Node, sr:&SimulateResult, pcur:Piece) {
    fn backup(leaf_pcur:Piece, node:*mut Node, sr:&SimulateResult, pcur:Piece) {
        if ! node.is_null() {
            let node = unsafe { node.as_mut().unwrap() };
            let win_way = (2.0 * ((node.pcur == leaf_pcur) as i32 as f32)) - 1.0;
            let dw = {
                let x = js::random_range_u(0, 101) as f32 / 100.0;
                ((2.0 * x) - 1.0).powf(2.0).max(1.0 / (1.0 + node.n_select))
            };
            node.n_avg_wins += win_way * sr.end_way * dw;
            node.n_avg_whole += win_way * sr.whole * dw;
            node.n_select += 1.0;
            backup(leaf_pcur, node.parent, sr, pcur);
        }
    }
    backup(node.pcur, node as *mut Node, sr, pcur)
}

type SimHashs = HashMap<u64,Board>;
fn step_uct<FnYield: Fn(&str)>(pcur:Piece, board:&Board, times_max:usize, level:usize, fn_yield:FnYield) -> Vec<Loc> {
    let mut locs:Vec<Loc> = Vec::new();
    let mut root = Node::new(ptr::null_mut(), pcur.side(), board, engine::loc_invalid(), 0.0);
    let phase = (((board.n_black() + board.n_white()) as f32 - 4.0) / (N_PIECES_IN_ENDING-4) as f32).max(0.0);
    let ucb_c = UCB_C - (UCB_C_DW * phase);
    let mut sim_hashs = SimHashs::with_capacity(UCT_SIMULATE_HASH_SIZE);
    js::log_str(&format!("sslash | phase:{phase:.3} | ucb-c:{ucb_c:.3} | init-moves {:?}", root.next_moves));
    if root.next_moves.len() > 1 {
        let mut time_board = board.clone();
        let yield_rate = (times_max / 64).clamp(256, 1024);
        let max_try_times = LEVEL_UCT_MAP[level.clamp(0, 9)] as f32;
        let max_try_times = (max_try_times + (phase * max_try_times)) as usize;
        for times in 0..times_max {
            let node = uct_select(&mut root, &mut time_board, ucb_c);
            let mut sr = SimulateResult::default();
            for _ in 0..max_try_times {
                sr = uct_simulate(&mut time_board, node);
                if let Some(hb) = sim_hashs.get(&sr.board.hash())  {
                    if *hb != sr.board {
                        sim_hashs.insert(sr.board.hash(), sr.board);
                    }
                } else {
                    sim_hashs.insert(sr.board.hash(), sr.board);
                    break;
                }
            }
            uct_backup(node, &sr, pcur);
            time_board.load(board);
            if times % yield_rate == 0 {
                fn_yield(&format!("UCT {}", times_max-times));
            }
        }
        let select_score = {
            move |node:&Node| {
                node.n_select
            }
        };
        root.nexts.sort_by(|a,b| {
            let b = select_score(b);
            let a = select_score(a);
            b.total_cmp(&a)
        });
        for node in &root.nexts {
            js::log_str(&format!("sslash | {} Q={:.3} W={:.3} N={}", node.loc, node.avg_wins(), node.avg_whole(), node.n_select));
        }
        let best_node = &root.nexts[0];
        js::log_str(&format!("sslash | best {}", best_node.loc));
        locs.push(best_node.loc);
    } else if root.next_moves.len() == 1 {
        locs.push(root.next_moves[0]);
    }
    locs
}

fn moves_sort(pcur:Piece, board:&Board, moves:&Vec<Loc>) -> Vec<Loc> {
    type Item = (f32,Loc);
    let mut items:Vec<Item> = moves.iter().map(|loc|{
        let mut node_board = board.clone();
        node_board.down(*loc, pcur);
        (valuation_whole(&node_board, pcur), *loc)
    }).collect();
    items.sort_by(|a:&Item,b:&Item| {
        b.0.total_cmp(&(a.0))
    });
    items.iter().map(|a|{a.1}).collect()
}
fn tree_fucker(pcur:Piece, board:&Board, depth:usize, mut alpha:f32, beta:f32, count:&mut usize) -> f32 {
    if depth > 0 {
        let mut moves = board.moves(pcur);
        if moves.len() > 0 {
            let mut node_board = board.clone();
            for loc in moves_sort(pcur, board, &mut moves) {
                node_board.down(loc, pcur);
                let value = -tree_fucker(pcur.side(), &node_board, depth-1, -beta, -alpha, count);
                alpha = alpha.max(value);
                if alpha >= beta {break}
                node_board.load(board);
            }
            alpha
        } else {
            -tree_fucker(pcur.side(), board, depth-1, -beta, -alpha, count)
        }
    } else {
        *count += 1;
        valuation_whole(board, pcur)
    }
}
fn step_fuck<F:Fn(&str)>(pcur:Piece, board:&Board, locs:&[Loc], level:usize, fn_yield:F) -> (f32,Loc,usize) {
    let depth_max = LEVEL_FUCK_MAP[level];
    let mut count = 0;
    let mut hit_value = 1.0;
    let mut hit_loc = locs[0];
    let mut beta = f32::MAX;
    for loc in locs {
        let mut node_board = board.clone();
        node_board.down(*loc, pcur);
        fn_yield(&format!("Search {}", count));
        let value = tree_fucker(pcur.side(), &node_board, depth_max, f32::MIN, f32::MAX, &mut count);
        if value < hit_value {
            hit_value = value;
            hit_loc = *loc;
            beta = value;
        }
    }
    let win_rate = if hit_value < 0.0 {-hit_value} else {1.0 - hit_value};
    (win_rate, hit_loc, count)
}

fn tree_ender(pcur:Piece, board:&Board, depth:usize, mut alpha:f32, mut beta:f32) -> f32 {
    if depth > 0 {
        let mut moves = board.moves(pcur);
        if moves.len() > 0 {
            let mut node_board = board.clone();
            if pcur == Piece::White {
                for loc in moves_sort(pcur, board, &mut moves) {
                    node_board.down(loc, pcur);
                    let node_value = tree_ender(pcur.side(), &node_board, depth-1, alpha, beta);
                    if node_value > alpha {
                        alpha = node_value
                    }
                    if alpha >= beta {break}
                    node_board.load(board);
                }
                alpha
            } else {
                for loc in moves_sort(pcur, board, &mut moves) {
                    node_board.down(loc, pcur);
                    let node_value = tree_ender(pcur.side(), &node_board, depth-1, alpha, beta);
                    if node_value < beta {
                        beta = node_value
                    }
                    if alpha >= beta {break}
                    node_board.load(board);
                }
                beta
            }
        } else {
            tree_ender(pcur.side(), board, depth-1, alpha, beta)
        }
    } else {
        valuation_n_pieces(board.n_black(), board.n_white())
    }
}
fn step_full_tree<F:Fn(&str)>(pcur:Piece, board:&Board, locs:&[Loc], depth_max:usize, fn_yield:F) -> (f32,Loc) {
    let mut hit = (-pcur.way(), locs[0]);
    let mut locs:Vec<Loc> = locs.into();
    while ! locs.is_empty() {
        let li = js::random_range_u(0, locs.len());
        let loc = locs.swap_remove(li);
        let mut node_board = board.clone();
        node_board.down(loc, pcur);
        fn_yield(&format!("In {}", loc));
        let value = tree_ender(pcur.side(), &node_board, depth_max, -1.0, 1.0);
        if pcur == Piece::Black && value < hit.0 {
            hit = (value, loc);
            if value <= -1.0 {break}
        } else if pcur == Piece::White && value > hit.0 {
            hit = (value, loc);
            if value >= 1.0 {break}
        }
    }
    hit
}

pub fn step<F1: Fn(&str), F2: Fn(&str)>(pcur:Piece, board:&Board, fn_yield:F1, fn_say:F2) -> Vec<Loc> {
    let init_moves = board.moves(pcur);
    if init_moves.len() > 1 {
        let n_black = board.n_black();
        let n_white = board.n_white();
        let n_piece = n_black + n_white;
        if n_piece < N_PIECES_IN_MID {
            let has_in_border = init_moves.iter().any(|loc| {LOCS_BEGIN_RANGOM[*loc as usize] != 1});
            if ! has_in_border {
                let mut scores:Vec<(Loc,f32)> = init_moves.iter().map(|&loc| {
                    let mut board_next = board.clone();
                    board_next.down(loc, pcur);
                    let a = valuation_being(&board_next);
                    let a = if pcur == Piece::Black {-a} else {a};
                    (loc,a)
                }).collect();
                scores.sort_by(|a,b|{b.1.total_cmp(&a.1)});
                if scores.len() > 2 {
                    scores.truncate(2);
                }
                js::log_str(&format!("sslash | random {:?}", scores));
                return scores.iter().map(|a|{a.0}).collect();
            }
        }
        //
        let level = unsafe { PARAMS.get().unwrap().level };
        if n_piece < N_PIECES_IN_FUCK {
            let uct_times_max = unsafe { PARAMS.get().unwrap().uct_times };
            step_uct(pcur, board, uct_times_max, level, fn_yield)
        } else if n_piece < N_PIECES_IN_ENDING {
            let (win_rate, loc, count) = step_fuck(pcur, board, &init_moves, level, fn_yield);
            js::log_str(&format!("sslash | search L{} | [{}] W={:.3} N={}", level, loc, win_rate, count));
            fn_say(&format!("{:.0}%", win_rate * 100.0));
            vec![loc]
        } else {
            let (value,loc) = step_full_tree(pcur, board, &init_moves, BOARD_LEN - N_PIECES_IN_ENDING, fn_yield);
            js::log_str(&format!("sslash | tree | V={} L={}", value, loc));
            if ((pcur == Piece::Black) && (value <= -1.0)) || ((pcur == Piece::White) && (value >= 1.0)) {
                fn_say("(●'◡'●)")
            } else if ((pcur == Piece::Black) && (value <= 0.0)) || ((pcur == Piece::White) && (value >= 0.0)) {
                fn_say("(★ ω ★)")
            } else if ((pcur == Piece::Black) && (value >= 1.0)) || ((pcur == Piece::White) && (value <= 1.0)) {
                fn_say("(T_T)")
            } else {
                fn_say("ಠ_ಠ")
            }
            vec![loc]
        }
    } else {init_moves}
}

macro_rules! param_set_range {
    ($id:ident, $to:expr, $min:expr, $max:expr) => {
        unsafe {
            let a = PARAMS.get_mut().unwrap();
            let b = $to.clamp($min, $max);
            a.$id = b;
        }
    };
}

pub mod params {
    use super::*;
    pub fn set_uct_times(to:usize) -> bool {
        param_set_range!(uct_times, to, UCT_TIMES_MIN, UCT_TIMES_MAX);
        true
    }
    pub fn set_level(to:usize) -> bool {
        param_set_range!(level, to, LEVEL_MIN, LEVEL_MAX);
        true
    }
}

pub fn gc() {}

fn _test<F:Fn(&str,&str)>(fn_call:F) {
    fn make_init_board() -> Board {
        let mut board = Board::default();
        board.set(44, Piece::Black);
        board.set(55, Piece::Black);
        board.set(45, Piece::White);
        board.set(54, Piece::White);
        board
    }
    fn make_random_board(min_n_piece:usize, max_n_piece:usize) -> Board {
        let mut board = make_init_board();
        let mut side = Piece::Black;
        let mut n_piece = js::random_range_u(min_n_piece, max_n_piece+1) - 4;
        let mut n_pass = 0;
        while (n_piece > 0) && (n_pass < 1) {
            let moves = board.moves(side);
            if moves.is_empty() {
                n_pass += 1
            } else {
                let loc = moves[js::random_range_u(0, moves.len())];
                board.down(loc, side);
                n_piece -= 1;
                n_pass = 0;
            }
            side = side.side();
        }
        board
    }
    fn _piece_n() {
        for _ in 0..BOARD_LEN {
            let mut n_black = 0;
            let mut n_white = 0;
            let mut board = Board::default();
            for loc in 0..BOARD_LEN as Loc {
                let p = match js::random_range_u(0, 3) {
                    0 => {Piece::Empty}
                    1 => {n_black += 1; Piece::Black}
                    2 => {n_white += 1; Piece::White}
                    _ => {
                        unreachable!()
                    }
                };
                board.set(loc, p);
            }
            assert_eq!(board.n_black(), n_black);
            assert_eq!(board.n_white(), n_white);
        }
    }
    _piece_n();
    let _moves = || {
        let board = Board::default();
        for loc in 0..BOARD_LEN as Loc {
            assert_eq!(board.get(loc), Piece::Empty);
        }
        assert_eq!(board.n_black(), 0);
        assert_eq!(board.n_white(), 0);
        assert_eq!(board.moves(Piece::Black), []);
        assert_eq!(board.moves(Piece::White), []);
        let mut board = make_init_board();
        assert_eq!(board.n_black(), 2);
        assert_eq!(board.n_white(), 2);
        let locs = board.moves(Piece::Black);
        for loc in locs {
            assert!(board.can_move(loc, Piece::Black));
        }
        let locs = board.moves(Piece::White);
        for loc in locs {
            assert!(board.can_move(loc, Piece::White));
        }
        board.down(35, Piece::Black);
        assert_eq!(board.moves(Piece::Black), [53, 63, 64]);
        assert_eq!(board.moves(Piece::White), [34, 36, 56]);
        assert_eq!(board.get(35), Piece::Black);
        assert_eq!(board.get(45), Piece::Black);
        assert_eq!(board.n_black(), 4);
        assert_eq!(board.n_white(), 1);

        let check_moves = |pcur:Piece, locs:&Vec<Loc>, board:&Board| {
            let mut loc_bits:u128 = 0;
            for loc in locs {
                loc_bits.set_bit_le(*loc);
            }
            for loc in 0..BOARD_LEN as Loc {
                let is_can_move = board.can_move(loc, pcur);
                if loc_bits.test_bit_le(loc) {
                    assert!(board.get(loc) == Piece::Empty);
                    if ! is_can_move {
                        let can_moves:Vec<Loc> = (0..BOARD_LEN as u32).filter(|loc|{
                            board.can_move(*loc, pcur)
                        }).collect();
                        let report = format!("board:\n{}\npcur:{pcur} loc:{loc} is not can-move, moves:{:?} can-moves:{:?}", board.to_display(), locs, can_moves);
                        js::log_str(&report);
                        let ps_all = board.make_ps_all();
                        for i in 0..54 {
                            js::log_str(&format!("ps[{}]: {:?}", i, ps_all[i]));
                        }
                        fn_call("view-board", &board.to_text(pcur));
                    }
                    assert!(is_can_move);
                } else if is_can_move {
                    let can_moves:Vec<Loc> = (0..BOARD_LEN as u32).filter(|loc|{
                        board.can_move(*loc, pcur)
                    }).collect();
                    let report = format!("board:\n{}\npcur:{pcur} loc:{loc} is can-move, moves:{:?} can-moves:{:?}", board.to_display(), locs, can_moves);
                    js::log_str(&report);
                    fn_call("view-board", &board.to_text(pcur));
                    assert!(! is_can_move);
                }
            }
        };
        for _ in 0..2048 {
            let board = make_random_board(4, BOARD_LEN);
            check_moves(Piece::Black, &board.moves(Piece::Black), &board);
            check_moves(Piece::White, &board.moves(Piece::White), &board);
        }
    };
    _moves();
}

pub fn init<F:Fn(&str,&str)>(fn_call:F) -> bool {
    unsafe {
        PARAMS.get_or_init(|| {Params::default()});
    }
    _test(fn_call);
    js::log_str("[WASM] TEST OK");
    true
}
